using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Criterion;
using Telerik.Web.Mvc;
using Telerik.Web.Mvc.Infrastructure;
using TelerikMvcGridCustomBindingHelper.Aggregates;
using TelerikMvcGridCustomBindingHelper.Mapper;
using TelerikMvcGridCustomBindingHelper.NHibernate.Aggregates;
using TelerikMvcGridCustomBindingHelper.NHibernate.DynamicQuery;
using TelerikMvcGridCustomBindingHelper.NHibernate.ORMIntegration;
using TelerikMvcGridCustomBindingHelper.NHibernate.Util;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper.NHibernate
{
    /// <summary>
    /// Helper class to work with Telerik Grid for ASP.NET MVC using CustomBinding mode coupled with
    /// some mapping techniques to convert Entities objects into ViewModels objects.
    /// </summary>
    /// <typeparam name="TEntity">The type of your Entity class</typeparam>
    /// <typeparam name="TViewModel">The type of your ViewModel class</typeparam>
    public class NHibernateGridCustomBindingHelper<TEntity, TViewModel> : BaseGridCustomBindingHelper<TEntity, TViewModel>
        where TViewModel : class
        where TEntity : class
    {
        #region Fields

        private readonly IQueryOver<TEntity, TEntity> _dataSource;
        private readonly IQueryOver<TEntity, TEntity> _dataSourceToCount;
        private Func<IQueryOver<TEntity, TEntity>, IFutureValue<object>> _queryOverExpressionCustomAggregateFunction;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="NHibernateGridCustomBindingHelper{TEntity,TViewModel}"/> class.
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">IQueryable&lt;TEntity, TEntity&gt; to query the database</param>
        /// <param name="dataSourceToCount">IQueryable&lt;TEntity, TEntity&gt; only for count the total of elements in the database</param>
        /// <param name="defaultSortMemberName">Default sort member name for this entity</param>
        public NHibernateGridCustomBindingHelper(GridCommand command, IQueryOver<TEntity, TEntity> dataSource, IQueryOver<TEntity, TEntity> dataSourceToCount, string defaultSortMemberName = null)
            : base(command, defaultSortMemberName)
        {
            _dataSource = dataSource;

            _dataSourceToCount = dataSourceToCount;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NHibernateGridCustomBindingHelper{TEntity,TViewModel}"/> class.
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">IQueryable&lt;TEntity, TEntity&gt; to query and count the total of elements in the database</param>
        public NHibernateGridCustomBindingHelper(GridCommand command, IQueryOver<TEntity, TEntity> dataSource)
            : this(command, dataSource, null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="NHibernateGridCustomBindingHelper{TEntity,TViewModel}"/> class.
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        public NHibernateGridCustomBindingHelper(GridCommand command)
            : this(command, null, null) { }

        #endregion

        #region Helper Functions

        protected override void EnsureDataSourceIsProcessed()
        {
            if (IsDataSourceProcessed) return;

            using (var ormIntegrationHolder = new NHibernateIntegrationHolder())
            {
                try
                {
                    ormIntegrationHolder.BeginTransaction();

                    var data = _dataSource ?? ormIntegrationHolder.QueryOver<TEntity>();

                    if (IgnoreGroupAggregatesField == false)
                        CommandField.GroupDescriptors.CreateAggregates(CommandField.Aggregates);

                    var dataSourceToCount = _dataSourceToCount ?? data.Clone();

                    var futureTotal = ApplyTotal(dataSourceToCount, data, CommandField);
                    AggregatesField = ApplyAggregates(dataSourceToCount, CommandField.Aggregates, futureTotal);

                    data = ApplyFiltering(data, CommandField.FilterDescriptors);

                    ApplyDefaultSorting(CommandField, DefaultSortMemberName);

                    data = ApplySorting(data, CommandField.SortDescriptors, CommandField.GroupDescriptors);

                    data = ApplyPaging(data, CommandField.Page, CommandField.PageSize);

                    ViewModelsField = ConvertEntitiesToViewModels(data);
                    var tempGroups = ApplyGrouping(ViewModelsField, CommandField.GroupDescriptors);

                    ProcessedDataField = FixFirstAndLastGroupAggregates(dataSourceToCount, tempGroups, CommandField.GroupDescriptors, CommandField.FilterDescriptors);
                    TotalField = (long)Reflector.ChangeType(futureTotal.Value, typeof(long));

                    ormIntegrationHolder.CommitTransaction();
                }
                catch
                {
                    ormIntegrationHolder.RollBackTransaction();
                    throw;
                }
                finally
                {
                    IsDataSourceProcessed = true;
                }
            }
        }

        private IEnumerable<TViewModel> ConvertEntitiesToViewModels(IQueryOver<TEntity, TEntity> data)
        {
            EntitiesField = UseProjectionsField ? data.Project(ProjectionsOptions).From<TViewModel>().ToList() : data.List();

            return GridModelMapper.Map<TEntity, TViewModel>(EntitiesField);
        }

        #endregion

        #region ApplyAggregates

        private IEnumerable FixFirstAndLastGroupAggregates(IQueryOver<TEntity> queryable, IEnumerable groups, IList<GroupDescriptor> groupDescriptors, IList<IFilterDescriptor> filterDescriptors)
        {
            var aggregateFunctions = groups as IList<AggregateFunctionsGroup>;

            if (aggregateFunctions == null)
                return groups;

            if (aggregateFunctions.Count >= 1)
            {
                var firstGroup = aggregateFunctions.First();
                ((CustomAggregateFunctionsGroup)aggregateFunctions.First()).Aggregates = GetAggregatesForGroup(queryable, firstGroup, groupDescriptors, filterDescriptors);
            }

            if (aggregateFunctions.Count >= 2)
            {
                var lastGroup = aggregateFunctions.Last();
                ((CustomAggregateFunctionsGroup)aggregateFunctions.Last()).Aggregates = GetAggregatesForGroup(queryable, lastGroup, groupDescriptors, filterDescriptors);
            }

            return aggregateFunctions;
        }

        private Dictionary<string, object> GetAggregatesForGroup(IQueryOver<TEntity> queryOver, IGroup @group, IList<GroupDescriptor> groupDescriptors, IList<IFilterDescriptor> filterDescriptors)
        {
            var filters = BuildGroupFilterDescriptors(@group, groupDescriptors, filterDescriptors);

            var filteredGroupQuery = ApplyFiltering(queryOver.Clone(), filters);

            var aggregateDescriptors = BuildGroupAggregateDescriptors(groupDescriptors);

            return ApplyAggregates(filteredGroupQuery, aggregateDescriptors);
        }

        private Dictionary<string, object> ApplyAggregates(IQueryOver<TEntity, TEntity> queryOver, IEnumerable<AggregateDescriptor> aggregateDescriptors, IFutureValue<object> reusableCount = null)
        {
            var containers = new List<CustomAggregatesContainer>();

            foreach (var aggregateFunction in aggregateDescriptors.SelectMany(x => x.Aggregates))
            {
                string entityProperty;
                var viewModelProperty = aggregateFunction.SourceField;

                try
                {
                    entityProperty = GetGridPropertyInfo(viewModelProperty).PropertyPath;
                }
                catch
                {
                    entityProperty = viewModelProperty;
                }

                switch (aggregateFunction.AggregateMethodName)
                {
                    case "Sum":
                        var sumFutureValue = AggregatesHelper.Sum(entityProperty, queryOver);
                        containers.Add(new CustomAggregatesContainer(viewModelProperty, sumFutureValue, AggregateMethod.Sum));
                        break;
                    case "Count":
                        reusableCount = reusableCount ?? AggregatesHelper.Count(entityProperty, queryOver);
                        containers.Add(new CustomAggregatesContainer(viewModelProperty, reusableCount, AggregateMethod.Count));
                        break;
                    case "Average":
                        var averageFutureValue = AggregatesHelper.Avg(entityProperty, queryOver);
                        containers.Add(new CustomAggregatesContainer(viewModelProperty, averageFutureValue, AggregateMethod.Average));
                        break;
                    case "Min":
                        var minFutureValue = AggregatesHelper.Min(entityProperty, queryOver);
                        containers.Add(new CustomAggregatesContainer(viewModelProperty, minFutureValue, AggregateMethod.Min));
                        break;
                    case "Max":
                        var maxFutureValue = AggregatesHelper.Max(entityProperty, queryOver);
                        containers.Add(new CustomAggregatesContainer(viewModelProperty, maxFutureValue, AggregateMethod.Max));
                        break;
                    case "Custom":
                        if (_queryOverExpressionCustomAggregateFunction != null)
                            containers.Add(new CustomAggregatesContainer(viewModelProperty, _queryOverExpressionCustomAggregateFunction.Invoke(queryOver), AggregateMethod.Custom));
                        else if (InheritedClassCustomAggregateCalculatorField != null)
                            containers.Add(new CustomAggregatesContainer(viewModelProperty, ((CustomAggregateFunction)InheritedClassCustomAggregateCalculatorField).GetValue(queryOver), AggregateMethod.Custom));
                        break;
                }
            }

            var aggregates = new Dictionary<string, AggregatesContainer>();

            foreach (var function in containers)
            {
                aggregates.Push(function.Property, function.FutureValue.Value, function.AggregateMethod);
            }

            return aggregates.ToDictionary(x => x.Key, x => (object)x.Value.GetValues());
        }

        #endregion

        #region Fluent API

        public new NHibernateGridCustomBindingHelper<TEntity, TViewModel> AddValueResolver<TQueryValueResolver>(Expression<Func<TViewModel, object>> viewModelProperty) where TQueryValueResolver : class, IQueryValueResolver
        {
            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)base.AddValueResolver<TQueryValueResolver>(viewModelProperty);
        }

        public new NHibernateGridCustomBindingHelper<TEntity, TViewModel> AddValueResolver<TQueryValueResolver>() where TQueryValueResolver : class, IQueryValueResolver
        {
            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)base.AddValueResolver<TQueryValueResolver>();
        }

        public new NHibernateGridCustomBindingHelper<TEntity, TViewModel> AddValueResolver(IQueryValueResolver queryValueResolver)
        {
            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)base.AddValueResolver(queryValueResolver);
        }

        public new NHibernateGridCustomBindingHelper<TEntity, TViewModel> UseCaseInsensitiveSearch(bool useCaseInsensitiveSearchOnBothClientAndServerSide = true)
        {
            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)base.UseCaseInsensitiveSearch(useCaseInsensitiveSearchOnBothClientAndServerSide);
        }

        public new NHibernateGridCustomBindingHelper<TEntity, TViewModel> UseCaseInsensitiveSearch(Action<ICaseOptions<TEntity>> caseOptions)
        {
            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)base.UseCaseInsensitiveSearch(caseOptions);
        }

        /// <summary>
        /// Adds a custom aggregate function.
        /// </summary>
        /// <typeparam name="TAggregateFunction">A class that inherits from <see cref="CustomAggregateFunction"/>.</typeparam>
        /// <param name="targetProperty">The target property.</param>
        /// <returns>This same <see cref="NHibernateGridCustomBindingHelper{TEntity,TViewModel}"/>.</returns>
        public NHibernateGridCustomBindingHelper<TEntity, TViewModel> AddAggregateFunction<TAggregateFunction>(Expression<Func<TViewModel, object>> targetProperty) where TAggregateFunction : CustomAggregateFunction, new()
        {
            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)AddAggregateFunctionBase<TAggregateFunction>(targetProperty);
        }

        /// <summary>
        /// Adds a custom aggregate function.
        /// </summary>
        /// <param name="targetProperty">The target property.</param>
        /// <param name="queryOverfunction">An inline function that represents a custom aggregate function.</param>
        /// <param name="queryablefunction">An inline function that represents a custom aggregate function.</param>
        /// <returns>This same <see cref="NHibernateGridCustomBindingHelper{TEntity,TViewModel}"/>.</returns>
        public NHibernateGridCustomBindingHelper<TEntity, TViewModel> AddAggregateFunction(Expression<Func<TViewModel, object>> targetProperty, Func<IQueryOver<TEntity>, IFutureValue<object>> queryOverfunction, Func<IQueryable<TEntity>, object> queryablefunction)
        {
            _queryOverExpressionCustomAggregateFunction = queryOverfunction;
            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)AddAggregateFunctionBase(targetProperty, queryablefunction);
        }

        public NHibernateGridCustomBindingHelper<TEntity, TViewModel> AddAggregateFunction(Expression<Func<TViewModel, object>> targetProperty, Expression<Func<IQueryOver<TEntity, TEntity>, IFutureValue<object>>> queryOverfunction)
        {
            _queryOverExpressionCustomAggregateFunction = queryOverfunction.Compile();

            var queryablefunction = PredicateConverter.TryConvert(queryOverfunction);

            return (NHibernateGridCustomBindingHelper<TEntity, TViewModel>)AddAggregateFunctionBase(targetProperty, queryablefunction);
        }

        #endregion

        #region ApplyTotal

        private IFutureValue<object> ApplyTotal(IQueryOver<TEntity, TEntity> dataSourceToCount, IQueryOver<TEntity> dataSource, GridCommand command)
        {
            if (dataSourceToCount != null)
            {
                return ApplyFiltering(dataSourceToCount, command.FilterDescriptors)
                    .ToRowCountInt64Query().FutureValue<object>();
            }

            return dataSource.ToRowCountInt64Query().FutureValue<object>();
        }

        #endregion

        #region ApplyPaging

        private static IQueryOver<TEntity, TEntity> ApplyPaging(IQueryOver<TEntity, TEntity> queryOver, int pageIndex, int pageSize)
        {
            if (pageIndex > 1 && pageSize > 0)
            {
                var skip = (pageIndex - 1) * pageSize;
                queryOver.Skip(skip).Take(pageSize);
            }
            else if (pageSize > 0)
            {
                queryOver.Take(pageSize);
            }

            return queryOver;
        }

        #endregion

        #region ApplySorting

        private static IQueryOver<TEntity, TEntity> ApplySorting(IQueryOver<TEntity, TEntity> queryOver, IList<SortDescriptor> sortDescriptors, IList<GroupDescriptor> groupDescriptors = null)
        {
            if (groupDescriptors != null && groupDescriptors.Any())
            {
                foreach (var groupDescriptor in groupDescriptors.Reverse())
                {
                    if (sortDescriptors.ToList().Exists(x => x.Member.Equals(groupDescriptor.Member)))
                        sortDescriptors.Remove(sortDescriptors.Single(x => x.Member.Equals(groupDescriptor.Member)));

                    var sortDescriptor = new SortDescriptor
                                             {
                                                 Member = groupDescriptor.Member,
                                                 SortDirection = groupDescriptor.SortDirection
                                             };

                    sortDescriptors.Add(sortDescriptor);
                }
            }

            if (sortDescriptors.Any())
            {
                foreach (var sortDescriptor in sortDescriptors.Reverse())
                {
                    var gridPropertyInfo = GetGridPropertyInfo(sortDescriptor.Member);

                    if (gridPropertyInfo.PropertyPath.IsNotNullOrWhiteSpace())
                    {
                        if (gridPropertyInfo.PropertyPath.Contains('.'))
                            ApplySortingForComplexProperties(queryOver, sortDescriptor, gridPropertyInfo);
                        else
                            ApplySortingForSimpleProperties(queryOver, sortDescriptor, gridPropertyInfo.PropertyPath);
                    }
                    else
                    {
                        ApplySortingForCustomExpressions(queryOver, sortDescriptor, gridPropertyInfo);
                    }
                }
            }

            return queryOver;
        }

        private static void ApplySorting(ICriteria criteria, SortDescriptor sortDescriptor, IProjection projection)
        {
            Order order = null;

            switch (sortDescriptor.SortDirection)
            {
                case ListSortDirection.Ascending:
                    order = Order.Asc(projection);
                    break;
                case ListSortDirection.Descending:
                    order = Order.Desc(projection);
                    break;
            }

            criteria.AddOrder(order);
        }

        private static void ApplySortingForCustomExpressions(IQueryOver queryOver, SortDescriptor sortDescriptor, GridPropertyInfo gridPropertyInfo)
        {
            var customExpression = gridPropertyInfo.CustomExpression;

            if (customExpression == null)
                throw new InvalidOperationException("Target property or expression for sort descriptor could not be found.");

            var sqlString = customExpression.ToSqlString();

            var propertiesPaths = customExpression.GetAllPropertiesPaths();

            foreach (var alias in propertiesPaths.Where(s => s.Contains('.')).Select(Generator.GetCriteriaAliasFor))
            {
                queryOver.CreateCriteria(alias);
            }

            IProjection projection = new BetterSqlProjection(sqlString, ignoreCast: true);

            ApplySorting(queryOver.RootCriteria, sortDescriptor, projection);
        }

        private static void ApplySortingForComplexProperties(IQueryOver queryOver, SortDescriptor sortDescriptor, GridPropertyInfo gridPropertyInfo)
        {
            var member = gridPropertyInfo.PropertyPath;

            var alias = Generator.GetCriteriaAliasFor(member);

            queryOver.CreateCriteria(alias);

            var property = global::NHibernate.Criterion.Projections.Property(alias.SourceValue);

            ApplySorting(queryOver.RootCriteria, sortDescriptor, property);
        }

        private static void ApplySortingForSimpleProperties(IQueryOver queryOver, SortDescriptor sortDescriptor, string member)
        {
            var property = global::NHibernate.Criterion.Projections.Property(member);
            ApplySorting(queryOver.RootCriteria, sortDescriptor, property);
        }

        #endregion

        #region ApplyFiltering

        private IQueryOver<TEntity, TEntity> ApplyFiltering(IQueryOver<TEntity, TEntity> queryOver, IList<IFilterDescriptor> filterDescriptors, FilterCompositionLogicalOperator logicalOperator = FilterCompositionLogicalOperator.And)
        {
            if (filterDescriptors.Any())
            {
                var dynamicWhereClause = new QueryOverDynamicWhereClause<TEntity, TViewModel>();

                foreach (var filterDescriptor in filterDescriptors.Reverse())
                {
                    if (filterDescriptor is CompositeFilterDescriptor)
                    {
                        var compositeFilterDescriptor = filterDescriptor as CompositeFilterDescriptor;
                        queryOver = ApplyFiltering(queryOver, compositeFilterDescriptor.FilterDescriptors, compositeFilterDescriptor.LogicalOperator);
                    }
                    else
                    {
                        var filter = (FilterDescriptor)filterDescriptor;
                        var whereClause = new QueryOverDynamicWhereClause<TEntity, TViewModel>(filter, CaseInsensitiveField, AcceptNullValuesWhenFilteringField, QueryValueResolvers);

                        if (logicalOperator == FilterCompositionLogicalOperator.And)
                        {
                            dynamicWhereClause.AndAlso(whereClause);
                        }
                        else
                        {
                            dynamicWhereClause.OrElse(whereClause);
                        }
                    }
                }

                if (dynamicWhereClause.HasFilters())
                {
                    queryOver = queryOver.Where(dynamicWhereClause);
                }
            }

            return queryOver;
        }

        #endregion

        /// <summary>
        /// An abstract class to help create a custom aggregate function.
        /// </summary>
        public abstract class CustomAggregateFunction : CustomAggregateFunctionBase
        {
            internal IList<TEntity> Entities;
            internal object GroupKeyValue;
            internal string GroupKeyName;

            protected internal sealed override object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup, IList<TEntity> soruceEntities, object groupKeyValue, string groupKeyName)
            {
                Entities = soruceEntities;
                GroupKeyValue = groupKeyValue;
                GroupKeyName = groupKeyName;

                return GetValueFromGroup(viewModelsGroup);
            }

            /// <summary>
            /// A custom aggregate function that will be executed against a grouped set of entities.
            /// </summary>
            /// <param name="viewModelsGroup">The IEnumerable&lt;TViewModel&gt; source group.</param>
            /// <returns>The result of the aggregate function.</returns>
            protected abstract object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup);

            /// <summary>
            /// A custom aggregate function.
            /// </summary>
            /// <param name="entities">The IQueryOver&lt;TEntity&gt; data source.</param>
            /// <returns>The result of the aggregate function as an IFutureValue&lt;object&gt;.</returns>
            public abstract IFutureValue<object> GetValue(IQueryOver<TEntity, TEntity> entities);
        }

        public abstract class LinqAggregateFunction : CustomAggregateFunction
        {
            protected sealed override object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup)
            {
                var entities = Entities != null
                    ? Entities.AsQueryable().Where(string.Format("{0} == @0", GroupKeyName), GroupKeyValue)
                    : GridModelMapper.Map<TViewModel, TEntity>(viewModelsGroup).AsQueryable();

                var queryablefunction = PredicateConverter.TryConvert(GetExpression());

                return queryablefunction.Invoke(entities);
            }

            public sealed override IFutureValue<object> GetValue(IQueryOver<TEntity, TEntity> entities)
            {
                return GetExpression().Compile().Invoke(entities);
            }

            protected abstract Expression<Func<IQueryOver<TEntity, TEntity>, IFutureValue<object>>> GetExpression();
        }

        public abstract class ExpressionAggregateFunction : CustomAggregateFunction
        {
            protected sealed override object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup)
            {
                var entities = Entities != null
                    ? Entities.AsQueryable().Where(string.Format("{0} == @0", GroupKeyName), GroupKeyValue)
                    : GridModelMapper.Map<TViewModel, TEntity>(viewModelsGroup).AsQueryable();

                var aggregate = GetExpression();

                switch (aggregate.AggregateMethod)
                {
                    case AggregateMethod.Sum:
                        return entities.Select(aggregate.Expression).Sum(o => (decimal?)o);
                    case AggregateMethod.Count:
                        return entities.Select(aggregate.Expression).Count();
                    case AggregateMethod.Average:
                        return entities.Select(aggregate.Expression).Average(o => (decimal?)o);
                    case AggregateMethod.Min:
                        return entities.Select(aggregate.Expression).Min(o => (decimal?)o);
                    case AggregateMethod.Max:
                        return entities.Select(aggregate.Expression).Max(o => (decimal?)o);
                    default:
                        throw new NotSupportedException(string.Format("The method or expression \"{0}\" is not supported.", aggregate.AggregateMethod));
                }
            }

            public sealed override IFutureValue<object> GetValue(IQueryOver<TEntity, TEntity> entities)
            {
                var aggregate = GetExpression();
                var fluentOperatorProjection = entities.Aggregate(aggregate.Expression);

                switch (aggregate.AggregateMethod)
                {
                    case AggregateMethod.Sum:
                        return fluentOperatorProjection.Sum();
                    case AggregateMethod.Count:
                        return fluentOperatorProjection.Count();
                    case AggregateMethod.Average:
                        return fluentOperatorProjection.Average();
                    case AggregateMethod.Min:
                        return fluentOperatorProjection.Min();
                    case AggregateMethod.Max:
                        return fluentOperatorProjection.Max();
                    default:
                        throw new NotSupportedException(string.Format("The method or expression \"{0}\" is not supported.", aggregate.AggregateMethod));
                }
            }

            protected abstract AggregateExpression GetExpression();

            protected AggregateExpression Sum(Expression<Func<TEntity, object>> expression)
            {
                return new AggregateExpression(AggregateMethod.Sum, expression);
            }

            protected AggregateExpression Count(Expression<Func<TEntity, object>> expression)
            {
                return new AggregateExpression(AggregateMethod.Count, expression);
            }

            protected AggregateExpression Average(Expression<Func<TEntity, object>> expression)
            {
                return new AggregateExpression(AggregateMethod.Average, expression);
            }

            protected AggregateExpression Min(Expression<Func<TEntity, object>> expression)
            {
                return new AggregateExpression(AggregateMethod.Min, expression);
            }

            protected AggregateExpression Max(Expression<Func<TEntity, object>> expression)
            {
                return new AggregateExpression(AggregateMethod.Max, expression);
            }

            protected class AggregateExpression
            {
                internal AggregateMethod AggregateMethod { get; private set; }
                internal Expression<Func<TEntity, object>> Expression { get; private set; }

                internal AggregateExpression(AggregateMethod aggregateMethod, Expression<Func<TEntity, object>> expression)
                {
                    AggregateMethod = aggregateMethod;
                    Expression = expression;
                }
            }
        }
    }

    /// <summary>
    /// Helper class to work with Asp.Net MCV and Telerik MVC Grid CustomBinding mode
    /// </summary>
    /// <typeparam name="TEntity">The type of your Entity class</typeparam>
    public class NHibernateGridCustomBindingHelper<TEntity> : NHibernateGridCustomBindingHelper<TEntity, TEntity> where TEntity : class
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NHibernateGridCustomBindingHelper{TEntity}"/> class.
        /// Default constructor
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">QueryOver&lt;TEntity, TEntity&gt; to query the database</param>
        /// <param name="dataSourceToCount">IQueryable&lt;TEntity, TEntity&gt; only for count the total of elements in the database</param>
        /// <param name="defaultSortMemberName">Default sort member name for this entity</param>
        public NHibernateGridCustomBindingHelper(GridCommand command, IQueryOver<TEntity, TEntity> dataSource, IQueryOver<TEntity, TEntity> dataSourceToCount, string defaultSortMemberName)
            : base(command, dataSource, dataSourceToCount, defaultSortMemberName) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="NHibernateGridCustomBindingHelper{TEntity}"/> class.
        /// Minimal constructor
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">QueryOver&lt;TEntity, TEntity&gt; to query and count the total of elements in the database</param>
        public NHibernateGridCustomBindingHelper(GridCommand command, IQueryOver<TEntity, TEntity> dataSource)
            : base(command, dataSource) { }
    }
}